#include "DB.h"
#include "BTreeIndex.h"
#include "Bitcask.h"
#include "LRUCache.h"
#include "Common.h"
#include "Objects.h"
#include "Log.h"


class PangDBCore
{
public:
    int Open(char *path);
    int Close();
    int Set(char *key, int key_size, char *val, int val_size, int mode);
    int Get(char *key, int key_size, char **val_large, char *val, int val_size_limit, int *val_size);
    int Delete(char *key, int key_size);
    void FreeVal(void *ptr);

private:
    int force_in_mem;
    int persistant;
    u64 mem_limit;

    LRUCache* cache;
    Bitcask* bitcask;
    BtreeIndex* index;
};


int PangDBCore::Open(char *path, int persistant_mode, int cache_mode, int close_mode, u64 max_mem_size)
{
    persistant = persistant_mode;
    cache_type = cache_mode;
    close_save_cache = close_mode;

    if (persistant = NO_PERSISTANT 
        && cache_type != FORCE_CACHE)
    {
        LOG_ERROR("DB Open options conflict: No_persistant must be together with force_cache!");
        return -1;
    }

    if (cache_type == PERSISTANT
        && cache_type == FORCE_CACHE
        && close_save_cache == CLOSE_NO_SAVE_CACHE)
    {
        LOG_ERROR("DB Open options conflict: Must choose CLOSE_NO_SAVE_CACHE while at PERSISTANT and FORCE_CACHE");
        return -1;
    }

    bitcask = (Bitcask*)mymalloc(sizeof(Bitcask));
    cache = (LRUCache*)mymalloc(sizeof(LRUCache));
    index = (BtreeIndex*)mymalloc(sizeof(BtreeIndex));

    index->Init();
    bitcask->Init(path, index, cache_type, persistant);
    cache->Init(1048576);
}

int PangDBCore::Close()
{
    bitcask->Close();
    bitcask->Destroy();
    index->Destroy();
    cache->Destroy();

    myfree(cache);
    myfree(index);
    myfree(bitcask);
}


void PangDBCore::FreeVal(void *ptr)
{
    myfree(ptr);
}


int PangDBCore::Set(char *key, int key_size, char *val, int val_size, int mode, bool fsync)
{
    if (key_size > 255) return -1;
    if (val_size > 256 * 256 * 256 - 1) return -1;
    Item *new_item, *old_item;

    if (cache_type == FORCE_CACHE && val_size > sizeof(void*))
        new_item = mymalloc(sizeof(Item) + key_size + val_size);
    else new_item = mymalloc(sizeof(Item) + key_size);

    if (new_item == NULL) 
    {
        LOG_ERROR("Can't alloc mem for size %d !", *val_size);
        return -1;
    }

    new_item->Init(key_size, val_size);

    if (persistant) new_item->real_pos = bitcask->Put(key, key_size, val, val_size, fsync);
    if (val_size <= sizeof(void*)) 
    {
        memcpy(new_item->cache, val, val_size);
        new_item->inner_data = 1;
    }
    else if (cache_type == FORCE_CACHE) memcpy(new_item->data + key_size, val, val_size);
    else if (cache_type == LRU_CACHE) cache->SetItem(new_item, val);

    old_item = index->Set(new_item, mode);
    if (old_item == NULL) 
    {
        if (persistant) bitcask->Delete(key, key_size, val_size, new_item->real_pos);
        if (cache_type == LRU_CACHE) cache->DeleteItem(new_item);
        myfree(new_item);
        return -1;
    }
    else if (old_item != new_item)
    {
        if (persistant) bitcask->Delete(key, key_size, val_size, old_item->real_pos);
        if (cache_type == LRU_CACHE) cache->DeleteItem(old_item);
        old_item->Delete();
    }

    return 0;
}

int PangDBCore::Get(char *key, int key_size, char **val_large, char *val, int val_size_limit, int *val_size)
{
    if (key_size > 255) return -1;
    char item_buf[sizeof(Item) + key_size];
    Item *anchor_item = &item_buf[0];
    Item *res_item = NULL; 
    bool got_data = false;
    int res = 0;
    
    anchor_item->Init(key_size, 0);
    memcpy(anchor_item->data, key, key_size);
    res_item = index->Get(anchor_item);
    if (res_item == NULL) return -1;
    *val_size = res_item->ValLength();
    if (*val_size > val_size_limit)
    {
        *val_large = mymalloc(*val_size);
        if (*val_large == NULL) 
        {
            res_item->ReleaseRef();
            LOG_ERROR("Can't alloc mem for size %d !", *val_size);
            return -1;
        }
        val = *val_large;
    }
    else *val_large = NULL;

    if (res_item->inner_data)
    {
        memcpy(val, &(res_item->cache), *val_size);
        got_data = true;
    }
    else if (cache_type == FORCE_CACHE)
    {
        memcpy(val, res_item->data + key_size, *val_size); 
        got_data = true;
    }
    else if (cache_type == LRU_CACHE) got_data = !(cache->GetItem(res_item, val));

    if (!got_data && persistant) res = bitcask->Get(val, *val_size, res_item->real_pos);

    res_item->ReleaseRef();
    return res;
}


int PangDBCore::Delete(char *key, int key_size, bool fsync)
{
    if (key_size > 255) return -1;
    char item_buf[sizeof(Item) + key_size];
    Item *anchor_item = &item_buf[0];
    Item *del_item = NULL;

    anchor_item->Init(key_size, 0);
    memcpy(anchor_item->data, key, key_size);
    del_item = index->Delete(anchor_item);
    if (del_item == NULL) return -1;

    if (cache_type == LRU_CACHE)  cache->DeleteItem(del_item);
    if (persistant) bitcask->Delete(key, key_size, del_item->ValLength(), del_item->real_pos, fsync);

    del_item->Delete();
    return 0;
}
